package top.jle.jpa.basic.dao;

import jakarta.annotation.Resource;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.criteria.*;
import org.hibernate.SessionFactory;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.ListPersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import top.jle.jpa.basic.exception.BasicCustomException;
import top.jle.jpa.basic.exception.BasicExceptionCode;

import java.util.*;

/**
 * 提供CriteriaQuery简便的创建和设置方法
 *
 * @author jim.lee
 *
 */
@Component
public class DetachedCriteriaFactory {
	private static final Logger LOGGER = LoggerFactory.getLogger(DetachedCriteriaFactory.class);
	@Resource
	private EntityManagerFactory entityManagerFactory;

	/**
	 * 利用PageRequest,PreFilter创建DetachedCriteria
	 *
	 */
	public <T> CriteriaQuery<T> buildDetachedCriteriaWithOrder(Class<T> clazz, PageRequest pageRequest,
																			  final PreFilter... preFilters) {
		HibernateCriteriaBuilder builder =	entityManagerFactory.unwrap(SessionFactory.class).getCriteriaBuilder();
		return this.buildDetachedCriteria(clazz,builder,pageRequest.getOrderBy(), preFilters);
	}


	public <T> List<Order> addOrderBySortKey(Root<T> root,CriteriaBuilder builder, SortKey... sortKeys) {

		if (sortKeys != null) {
			List<Order> orderList = new ArrayList<>();
			for (SortKey sortKeyCopy : sortKeys) {
				if (sortKeyCopy.isAscendingOrder()) {
					orderList.add(builder.asc(root.get(sortKeyCopy.getProperty())));
				} else {
					orderList.add(builder.desc(root.get(sortKeyCopy.getProperty())));
				}
			}
			return orderList;
		}
		return Collections.emptyList();
	}
	public <T> CriteriaQuery<T> buildDetachedCriteria(Class<T> clazz, HibernateCriteriaBuilder builder, final PreFilter... filters) {

		return buildDetachedCriteria(clazz,builder,null,filters);
	}
	private <T,X,Z> void handleFetch(Class<T> clazz, FetchParent<X,Z> fetchParent, MappingMetamodelImpl metamodel){
		EntityPersister entityPersister = metamodel.entityPersister(clazz);
		entityPersister.getAttributeMappings().forEach(attributeMapping -> {
			if(attributeMapping instanceof ToOneAttributeMapping t){
				Fetch<X,Z> fetch = fetchParent.fetch(t.getAttributeName(), JoinType.LEFT);
				if(fetch instanceof SqmAttributeJoin<X,Z> f){
					f.alias(f.getAttribute().getJavaType().getSimpleName());
				}
				handleFetch(fetch.getAttribute().getJavaType(),fetch,metamodel);
			}
		});
	}

	public <T> CriteriaQuery<T> buildDetachedCriteria(Class<T> clazz,HibernateCriteriaBuilder builder,SortKey[] sortKeys, final PreFilter... filters) {
		CriteriaQuery<T> query = builder.createQuery(clazz).distinct(true);
		Root<T> root = query.from(clazz);
		MappingMetamodelImpl metamodel = (MappingMetamodelImpl) entityManagerFactory.getMetamodel();
		handleFetch(clazz,root,metamodel);
		//添加参数提供的限制条件
		if (filters != null ) {
			query.where(Arrays.stream(filters).filter(Objects::nonNull).map(preFilter -> preFilter.buildCriterion(root,builder)).toArray(Predicate[]::new));
		}
		query.orderBy(addOrderBySortKey(root,builder,sortKeys));
		return query;
	}



	public <T> CriteriaQuery<T>  buildDetachedCriteriaWithOrder(Class<T> clazz, SortKey[] sortKeys, PreFilter... preFilter) {
		HibernateCriteriaBuilder builder =	entityManagerFactory.unwrap(SessionFactory.class).getCriteriaBuilder();
		return this.buildDetachedCriteria(clazz,builder, sortKeys,preFilter);
	}

	@SuppressWarnings("unchecked")
	public <T> CriteriaQuery<T> buildDetachedCriteriaByClassNameWithOrder(String className, SortKey[] sortKeys, final PreFilter... filters){

		try {
			HibernateCriteriaBuilder builder =	entityManagerFactory.unwrap(SessionFactory.class).getCriteriaBuilder();
			return buildDetachedCriteria((Class<T>) Class.forName(className),builder,sortKeys,filters);
		} catch (ClassNotFoundException e) {
			LOGGER.error(e.getMessage(),e);
			throw new BasicCustomException(BasicExceptionCode.SERVER_ERROR);
		}


	}



}
